﻿using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using ZetaLongPaths;

namespace Lztkdr.FileTransfer
{
    /// <summary>
    /// Md5校验类
    /// 大文件校验使用 分片Md5检验
    /// </summary>
    public class FileValidator
    {

        public FileTransfer FT { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath1">第1个文件路径</param>
        /// <param name="filePath2">第2个文件路径</param>
        /// <param name="chunkLength">每次随机抽取的字节数组大小</param>
        /// <param name="compareCount">要进行随机检验的次数</param>
        public FileValidator(FileTransfer ft, string filePath1, string filePath2, int chunkLength = 1024 * 1024 * 15, int gbValidateCount = 3)
        {
            this.FT = ft;
            this.fileInfo1 = new ZlpFileInfo(filePath1);
            this.fileInfo2 = new ZlpFileInfo(filePath2);

            this.MD5Provider = new MD5CryptoServiceProvider();

            this.ChunkLength = chunkLength;

            this.GBValidateCount = gbValidateCount;
        }
        public ZlpFileInfo fileInfo1 { get; private set; }
        public ZlpFileInfo fileInfo2 { get; private set; }
        public int ChunkLength { get; private set; }

        /// <summary>
        /// 大文件检验时，每GB检验次数
        /// </summary>
        public int GBValidateCount { get; set; }

        /// <summary>
        /// 大文件校验时，总共校验次数
        /// </summary>
        public long TotalValidateCount { get; private set; } = 1;


        /// <summary>
        ///大文件校验时 使用MD5服务类
        /// </summary>
        public MD5CryptoServiceProvider MD5Provider { get; set; }

        /// <summary>
        /// 正在进行MD5检测中
        /// </summary>
        public bool IsMD5CheckIng { get; set; }

        public string MD51
        {
            get
            {
                return ComputeMd5(this.fileInfo1.FullName);
            }
        }
        public string MD52
        {
            get
            {
                return ComputeMd5(this.fileInfo2.FullName);
            }
        }

        /// <summary>
        /// 检测文件是否一致
        /// </summary>
        /// <returns></returns>
        public bool IsSame()
        {
            try
            {
                this.IsMD5CheckIng = true;

                if (!this.fileInfo1.Exists || !this.fileInfo2.Exists)
                {
                    return false;
                }

                if (this.fileInfo1.Length != this.fileInfo2.Length)
                {
                    return false;
                }

                if (this.fileInfo1.Length <= (1024 * 1024 * 1024)) //1GB 以内使用 全文件 Md5验证
                {
                    return MD51.Equals(MD52);
                }
                else if (this.fileInfo1.Length <= (1024 * 1024 * 1024 * (long)10))// n GB 以内使用 全文件 memcmp方式 字节数组比较 
                {
                    using (FileStream fs1 = File.Open(this.fileInfo1.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                    {
                        using (FileStream fs2 = File.Open(this.fileInfo2.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                        {
                            byte[] buffer1 = null, buffer2 = null;
                            buffer1 = new byte[ChunkLength];
                            buffer2 = new byte[ChunkLength];

                            while (true)
                            {
                                if (this.FT.State == State.取消 || this.FT.State == State.暂停)
                                {
                                    return false;
                                }

                                var realLen1 = fs1.Read(buffer1, 0, this.ChunkLength);

                                var realLen2 = fs2.Read(buffer2, 0, this.ChunkLength);

                                if (realLen1 == 0 || realLen2 == 0)
                                {
                                    break;
                                }

                                if (Memcmp(buffer1, buffer2) != 0)
                                {
                                    return false;
                                }
                            }
                        }
                    }
                    return true;
                }
                else //1GB 以上，使用3次随机的 字节片段 抽取 进行Md5 验证
                {

                    long yShu = (fileInfo1.Length % (1024 * 1024 * 1024));

                    this.TotalValidateCount = (yShu > 0) ?
                        (fileInfo1.Length / (1024 * 1024 * 1024) * this.GBValidateCount + 1) : (fileInfo1.Length / (1024 * 1024 * 1024) * this.GBValidateCount);

                    for (int j = 0; j < this.TotalValidateCount; j++)
                    {
                        if (this.FT.State == State.取消 || this.FT.State == State.暂停)
                        {
                            return false;
                        }

                        var lg = NextLong(0, fileInfo1.Length - ChunkLength);//随机取片
                        var bl = CheckChunkReadMD5(this.fileInfo1.FullName, this.fileInfo2.FullName, lg);
                        if (!bl)
                        {
                            return false;
                        }
                    }
                    return true;
                }
            }
            finally
            {
                this.IsMD5CheckIng = false;
            }
        }

        private static Random Rdom = new Random();
        private long NextLong(long minValue, long maxValue)
        {
            if (minValue > maxValue)
            {
                throw new ArgumentException("minValue is great than maxValue", "minValue");
            }
            long num = maxValue - minValue;
            return minValue + (long)(Rdom.NextDouble() * num);
        }

        private string ComputeMd5(string filePath)
        {
            int bufferSize = 1024 * 1024 * 16;
            byte[] buffer = new byte[bufferSize];
            Stream inputStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
            HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider();
            int readLength = 0;//每次读取长度
            var output = new byte[bufferSize];
            while ((readLength = inputStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                if (this.FT.State == State.取消 || this.FT.State == State.暂停)
                {
                    return string.Empty;
                }

                hashAlgorithm.TransformBlock(buffer, 0, readLength, output, 0);
            }
            hashAlgorithm.TransformFinalBlock(buffer, 0, 0);
            string md5 = BitConverter.ToString(hashAlgorithm.Hash);
            hashAlgorithm.Clear();
            inputStream.Close();
            inputStream.Dispose();
            return md5;
        }

        private bool CheckChunkReadMD5(string filePath1, string filePath2, long skip)
        {
            //FileStream fs = null;
            //MD5CryptoServiceProvider md5Provider = null;
            //byte[] buffer = null;

            //fs = File.Open(filePath1, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
            //buffer = new byte[ChunkLength];
            //fs.Position = skip;
            //fs.Read(buffer, 0, buffer.Length);
            //md5Provider = new MD5CryptoServiceProvider();
            //string str1 = BitConverter.ToString(md5Provider.ComputeHash(buffer));
            //fs.Close();
            //fs.Dispose();
            //md5Provider.Clear();

            //fs = File.Open(filePath2, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
            //buffer = new byte[ChunkLength];
            //fs.Position = skip;
            //fs.Read(buffer, 0, buffer.Length);
            //md5Provider = new MD5CryptoServiceProvider();
            //string str2 = BitConverter.ToString(md5Provider.ComputeHash(buffer));
            //fs.Close();
            //fs.Dispose();
            //md5Provider.Clear();

            //return str1.Equals(str2);


            FileStream fs1 = null, fs2 = null;
            byte[] buffer1 = null, buffer2 = null;

            fs1 = File.Open(filePath1, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
            buffer1 = new byte[this.ChunkLength];
            fs1.Position = skip;
            fs1.Read(buffer1, 0, buffer1.Length);
            fs1.Close();
            fs1.Dispose();

            fs2 = File.Open(filePath2, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
            buffer2 = new byte[this.ChunkLength];
            fs2.Position = skip;
            fs2.Read(buffer2, 0, buffer2.Length);
            fs2.Close();
            fs2.Dispose();

            return Memcmp(buffer1, buffer2) == 0;
        }


        /// <summary>
        /// memcmp API
        /// </summary>
        /// <param name="b1">字节数组1</param>
        /// <param name="b2">字节数组2</param>
        /// <returns>如果两个数组相同，返回0；如果数组1小于数组2，返回小于0的值；如果数组1大于数组2，返回大于0的值。</returns>
        [DllImport("msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr memcmp(byte[] b1, byte[] b2, IntPtr count);

        /// <summary>
        /// 用memcmp比较字节数组
        /// </summary>
        /// <param name="b1">字节数组1</param>
        /// <param name="b2">字节数组2</param>
        /// <returns>如果两个数组相同，返回0；如果数组1小于数组2，返回小于0的值；如果数组1大于数组2，返回大于0的值。</returns>
        public static int Memcmp(byte[] b1, byte[] b2)
        {
            IntPtr retval = memcmp(b1, b2, new IntPtr(b1.Length));
            return retval.ToInt32();
        }

    }
}
